Hero

XMの取引結果を毎日自動でFreeeに連携するしくみを作る! その2 XMの取引履歴をメールから取得する。

Date: 2021/05/16 15:25

Category: none

はじめに

この記事は前回のつづき
であり、海外FX業者XMでの取引履歴を自動的に会計Freeeに反映するための仕組みを考えるものである。
今回は、前回分解したタスクの1つである、「XMの取引履歴の自動取得」を実現するための方法を考える。

詳しいGASの実装はこちらのレポジトリに保存している。

XMの取引履歴

海外FX取引業者のXMは、1日の取引が終了した後(日本時間の午前6時ごろ)にその日1日の取引結果、取引中のポジションなどをメールで通知してくれる。このメールは、htmlで書かれた表形式でいつどのような取引が行われたか、その結果どの程度損益が発生したかが記載されている。したがって、既に取引が成立(買った株や為替を売却、もしくは空売した為替を買い戻した状態)の結果をGASでメール記載の表から読み取り、各取引ごとにCSVに書き出せばとりあえず、取引履歴を取り扱いしやすい状態にできる。

メールにある取引履歴の例

HTMLで表が記載されている

GmailをパースしてCSV形式に整える

今回はメール本文のテーブル構造を読み取りCSVファイルに書き起こすため、Parserを使用した。

Parserの導入、詳しい使用方法はここを参考にすればいいと思う。

GASで取得したメール本文をdata()にセットし、form ~ to で開始地点と終了地点を指定するとその部分のhtmlだけ引き抜くことができる。

たとえば、以下のように定義されたhtmlを<div class="parent">から次に</div>が出るまでパースするコードの実行結果は次の画像のようになる。

1let text = '<div class="parent">\ 2 <p>親</p>\ 3 <div class="child">\ 4 <p>子1</p>\ 5 <div class="grand-child">孫1</div>\ 6 <div class="grand-child">孫2</div>\ 7 </div>\ 8 <div class="child">子2</div>\ 9 <p>親</p>\ 10</div>' 11 let parsed_html = Parser.data(text).from('<div class="parent">').to('</div>').iterate() 12 Logger.log(parsed_html) 13

実行結果

これを用いて、<tr></tr>の中にある、td要素、つまり表の1列を取得し、要素ごとに連想配列にでも格納してあげれば、表の情報からCSV形式を作り出すことは容易にできる。ただし、要素をどのように取得するかに関しては実際に取れる値を確認して対応する必要がある。たとえば、今回、要素をカンマ区切りで取得したOpenTimeの値は、

&nbsp;2021.05.13 23:00:00

のように不要な記号が含まれていたりする。

htmlのtd要素を最終的に以下のようにカラム名と値の連想配列のlist形式に変換する。

[{Size=0.10, S/L=108.426, T/P=0.000, ClosePrice=109.430, Ticket=34505399, Item=usdjpy, OpenPrice=109.419, Open Time=2021.05.13 23:00:00, Swap=-23, Close Time=2021.05.14 01:00:00, Profit=110, Type=buy, Commission=0},
{Type=buy, T/P=0.000, Ticket=34505619, Close Time=2021.05.14 01:00:40, Open Time=2021.05.14 00:15:38, Swap=0, Item=usdjpy, Size=0.10, ClosePrice=109.445, S/L=108.430, OpenPrice=109.430, Profit=150, Commission=0},
{Item=gbpusd, Size=0.10, Swap=-41, Profit=219, Open Time=2021.05.13 23:00:00, Commission=0, ClosePrice=1.40513, Close Time=2021.05.14 01:25:01, S/L=1.41033, Ticket=34505405, OpenPrice=1.40533, Type=sell, T/P=0.00000}, 
{Commission=0, Swap=0, Size=0.10, S/L=1.20275, Item=eurusd, Profit=789, Close Time=2021.05.14 07:00:00, Type=buy, OpenPrice=1.20775, T/P=0.00000, Open Time=2021.05.14 02:55:05, ClosePrice=1.20847, Ticket=34506233}]

これに、ヘッダーをつけて、ヘッダー通りに並べかえて、カンマ区切りのテキストに出力すればCSVの完成である。
引数でもらったヘッダー要素の配列を順番にカンマ区切りで結合し、
Ticketという項目の小さい順に並べて要素をカンマ区切りで結合するということをやっている。
(シンプルに書けるのはいいんだけど、認知負荷が高いのはなんとかならないのかな... とついつい思ってしまう)

1function json2csv(json_,ordered_keys) { 2 let header_text = ordered_keys.join(',')+'\n' 3 let values_text = json_.sort((a,b)=>a.Ticket - b.Ticket).map((row)=> ordered_keys.map((key) => row[key]).join(',')).join('\n') 4 return header_text + values_text 5} 6

これにより、最終的に以下のようなCSV形式のテキストを得ることができる。

Ticket,Open Time,Type,Size,Item,OpenPrice,S/L,T/P,Close Time,ClosePrice,Commission,Swap,Profit
34505399,2021.05.13 23:00:00,buy,0.10,usdjpy,109.419,108.426,0.000,2021.05.14 01:00:00,109.430,0,-23,110
34505405,2021.05.13 23:00:00,sell,0.10,gbpusd,1.40533,1.41033,0.00000,2021.05.14 01:25:01,1.40513,0,-41,219
34505619,2021.05.14 00:15:38,buy,0.10,usdjpy,109.430,108.430,0.000,2021.05.14 01:00:40,109.445,0,0,150
34506233,2021.05.14 02:55:05,buy,0.10,eurusd,1.20775,1.20275,0.00000,2021.05.14 07:00:00,1.20847,0,0,789

GASからS3にファイルを送る

作成したCSVのテキストを、AWSのS3に送る。
これにより、S3にファイルが置かれたことをトリガーに起動するLambdaで、集計用スプレッドシートに値を記録する処理の実行や転送に成功したことをLINEで通知したりすることができる。(別にGoogleDriveでもできるが、AWSをとにかく使いたかった...)

AWSに送る際には、こちらのS3ライブラリを使用させていただいた。

そのため、やることは、保存先のS3のbucketを用意すること、S3への書き込み権限のみに絞ったユーザーを作成し、(s3-put-only-policyを持たせたs3-put-only-userを作成した)アクセストークンを取得することとテキストデータをバイナリに変換することくらいである。
このトークンを直接ソースに保存するとこのGASをgit管理しづらいのでPropertyに登録した値を都度取得して実行している。

function csvPutS3(csv) {
  let props = PropertiesService.getScriptProperties()
  let accessKey = props.getProperty('AWS_ACCESS_KEY_ID')
  let secretKey = props.getProperty('AWS_SECRET_ACCESS_KEY')
  let bucketName = props.getProperty('S3_BUCKET_NAME')
  let csvFileName = 'histories.csv'
  // バイナリに変換
  let blob = Utilities.newBlob(csv)
  let s3 = S3.getInstance(accessKey, secretKey)
  s3.putObject(bucketName, csvFileName, blob, {logRequests:true})
}

あとはこのGASをメールが届いた後に実行するようにトリガーをセットすれば毎朝自動でS3にCSVファイルを送ることができる。

実行時のログの一部

転送されたhistories.csv

まとめ

GmailをGASでParserを使ってParseし、CSV形式のテキストを作成した。
作成したCSVデータをバイナリに変換して、S3のライブラリでS3に送ることができるようにした。

Hero

まさき。です。PHPエンジニアをやってます。

自分の課題を技術で乗り越えるの好きかもしれないです。

フロントエンドは苦手ですが、少しでもできるようになれたらな、ということでNextJSでこのブログサイトを作りました。